package com.imon.web.codesphere;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;

import com.imon.web.codesphere.data.FileSource;

import difflib.Chunk;
import difflib.Delta;
import difflib.DiffRow;
import difflib.DiffRowGenerator;
import difflib.Patch;

@Component
public class DiffProcessor {
	private static final Log LOG = LogFactory.getLog(DiffProcessor.class);

	@Resource
	private MongoDatabase database;

	public DiffData createDiff(final String path, final long oldRevision, final long revision) {
		final List<String> oldRevisionSourceList = getSourceAsList(path, oldRevision);
		final List<String> newRevisionSourceList = getSourceAsList(path, revision);

		LOG.debug(String.format("Old Revision - LOC: (%s), New Revision - LOC: (%s)", oldRevisionSourceList.size(),
				newRevisionSourceList.size()));

		final List<DiffRow> diffRows = new DiffRowGenerator.Builder().showInlineDiffs(true).build()
				.generateDiffRows(oldRevisionSourceList, newRevisionSourceList);

		final DiffData diffData = new DiffData(oldRevision, revision, diffRows);
		diffData.setFilePath(path);

		return diffData;
	}

	public List<String> applyPatch(final List<String> sourceList, final Patch patch,
			final Closure<Delta, Chunk> getChunk) {

		final List<String> copyList = new ArrayList<String>(sourceList);

		LOG.debug(String.format("before Patch(%s)", copyList.size()));

		for (final Delta delta : patch.getDeltas()) {

			final Chunk chunk = getChunk.handle(delta);

			// LOG.debug("Chunk:" + chunk);

			final int pos = chunk.getPosition();
			final int size = chunk.getSize();
			final List<String> lines = (List<String>) chunk.getLines();

			LOG.debug(String.format("ApplyPatch: pos=%d, size=%d, lines.size=%d", pos, size, lines.size()));

			final ArrayList<String> linesCopy = new ArrayList<String>(lines);

			if (lines.size() == 0) {
				copyList.add(pos, "noline");
			} else {
				markItemsAsChanged(pos, linesCopy.size(), copyList);
			}
		}

		LOG.debug(String.format("after Patch(%s)", copyList.size()));
		return copyList;
	}

	private void markItemsAsChanged(final int pos, final int size, final List<String> copyList) {
		LOG.debug(String.format("markItemsAsChanged(pos=%d, size=%d, copyList.size=%d)", pos, size, copyList.size()));
		for (int i = pos; i < pos + size; i++) {
			// LOG.debug(String.format("Setting pos: %s (%s)", i,
			// copyList.get(i)));
			copyList.set(pos, String.format("changed:%s", copyList.get(i)));
		}
	}

	private List<String> getSourceAsList(final String path, final long revision) {
		final Map<String, Object> criteria = createCriteria(path, revision);
		final FileSource revisionSource = database.loadSingle(FileSource.class, criteria, "-revision");

		return convertToList(revisionSource.getSourceCode());
	}

	private List<String> convertToList(final String sourceCode) {
		return Arrays.asList(StringUtils.splitByWholeSeparator(sourceCode, "\n"));
	}

	@SuppressWarnings("serial")
	private Map<String, Object> createCriteria(final String path, final long revision) {
		return new HashMap<String, Object>() {
			{
				put("fileUrl =", path);
				put("revision <=", revision);
			}
		};
	}
}
